跳到主要内容

JavaScript 函数基础

转载自 函数定义和调用

arguments 关键字获取参数

JavaScript 有一个关键字 arguments,它只在函数内部起作用,并且永远 指向当前函数的调用者传入的所有参数。arguments 类似 Array 但它不是一个 Array:

function foo(x) {
console.log('x = ' + x); // 10
for (var i=0; i < arguments.length; i++) {
console.log('arg ' + i + ' = ' + arguments[i]); // 10, 20, 30
}
}

foo(10, 20, 30);

利用 arguments,可以获得调用者传入的所有参数。也就是说,即使函数不定义任何参数,还是可以拿到参数的值:

function abs() {
if (arguments.length === 0) {
return 0;
}

var x = arguments[0];
return x >= 0 ? x : -x;
}

abs(); // 0
abs(10); // 10
abs(-9); // 9

实际上 arguments 最常用于判断传入参数的个数

// foo(a[, b], c)
// 接收 2~3 个参数,b是可选参数,如果只传2个参数,b默认为null:
function foo(a, b, c) {
if (arguments.length === 2) {
// 实际拿到的参数是a和b,c为undefined
c = b; // 把b赋给c
b = null; // b变为默认值
}
// ...
}

要把中间的参数 b 变为 “可选” 参数,就只能通过 arguments 判断,然后重新调整参数并赋值。

ES6 rest 可变参数

由于 JavaScript 函数允许接收任意个参数,于是我们就不得不用 arguments 来获取所有参数:

function foo(a, b) {
var i, rest = [];
if (arguments.length > 2) {
for (i = 2; i<arguments.length; i++) {
rest.push(arguments[i]);
}
}
console.log('a = ' + a);
console.log('b = ' + b);
console.log(rest);
}

为了获取除了已定义参数 a、b 之外的参数,我们不得不用 arguments,并且循环要从索引 2 开始以便排除前两个参数,这种写法很别扭,只是为了获得额外的 rest 参数,有没有更好的方法?

ES6 标准引入了 rest 参数,上面的函数可以改写为:

function foo(a, b, ...rest) {
console.log('a = ' + a);
console.log('b = ' + b);
console.log(rest);
}

foo(1, 2, 3, 4, 5);
// 结果:
// a = 1
// b = 2
// Array [ 3, 4, 5 ]

foo(1);
// 结果:
// a = 1
// b = undefined
// Array []

rest 参数只能写在最后,前面用 ... 标识,从运行结果可知,传入的参数先绑定 a、b,多余的参数以数组形式交给变量 rest,所以,不再需要 arguments 我们就获取了全部参数。

变量提升

JavaScript 的函数定义有个特点,它会先扫描整个函数体的语句,把所有申明的变量 “提升” 到函数顶部:

'use strict';

function foo() {
var x = 'Hello, ' + y;
console.log(x);
var y = 'Bob';
}

foo();

虽然是 strict 模式,但语句 var x = 'Hello, ' + y; 并不报错,原因是变量y在稍后申明了。但是 console.log 显示 Hello, undefined,说明变量 y 的值为 undefined。这正是因为 JavaScript 引擎自动提升了变量 y 的声明,但不会提升变量 y 的赋值。

对于上述 foo() 函数,JavaScript 引擎看到的代码相当于:

function foo() {
var y; // 提升变量y的申明,此时y为undefined
var x = 'Hello, ' + y;
console.log(x);
y = 'Bob';
}

由于 JavaScript 的这一怪异的 “特性”,我们在函数内部定义变量时,请严格遵守 “在函数内部首先申明所有变量” 这一规则。最常见的做法是用一个 var 申明函数内部用到的所有变量:

function foo() {
var
x = 1, // x初始化为1
y = x + 1, // y初始化为2
z, i; // z和i为undefined
// 其他语句:
for (i=0; i<100; i++) {
...
}
}

对象的方法

在一个对象中绑定函数,称为这个对象的方法。

在 JavaScript 中,对象的定义是这样的:

var xiaoming = {
name: '小明',
birth: 1990
};

但是,如果我们给 xiaoming 绑定一个函数,就可以做更多的事情。比如,写个 age() 方法,返回 xiaoming 的年龄:

var xiaoming = {
name: '小明',
birth: 1990,
age: function () {
var y = new Date().getFullYear();
return y - this.birth;
}
};

xiaoming.age; // function xiaoming.age()
xiaoming.age(); // 今年调用是25,明年调用就变成26了

在一个方法内部,this 是一个特殊变量,它始终指向当前对象,也就是 xiaoming 这个变量。所以,this.birth 可以拿到 xiaoming 的 birth 属性。

让我们拆开写:

function getAge() {
var y = new Date().getFullYear();
return y - this.birth;
}

var xiaoming = {
name: '小明',
birth: 1990,
age: getAge
};

xiaoming.age(); // 25, 正常结果
getAge(); // NaN

但是注意!单独调用函数 getAge() 会返回 NaN

如果以对象的方法形式调用,比如 xiaoming.age(),该函数的 this 指向被调用的对象,也就是 xiaoming,这是符合我们预期的。

如果单独调用函数,比如 getAge(),此时,该函数的 this 指向全局对象,也就是 window(如果是在 strict 模式下,它指向 undefined)

注意,这样写也是不行的:

var fn = xiaoming.age; // 先拿到xiaoming的age函数
fn(); // NaN

要保证 this 指向正确,必须用 obj.xxx() 的形式调用!

注意,这样写也是会报错的:

'use strict';

var xiaoming = {
name: '小明',
birth: 1990,
age: function () {
function getAgeFromBirth() {
var y = new Date().getFullYear();
return y - this.birth;
}
return getAgeFromBirth();
}
};

xiaoming.age(); // Uncaught TypeError: Cannot read property 'birth' of undefined

这里应该使用 const that = this